/*
* @author Ali Ok / Oleg Varaksin
* $$Id: NonSerializableBeanInspector.java 46 2011-01-14 21:58:05Z ovaraksin $$
*/

package com.googlecode.jsf2inspector.plugin.inspection;

import com.googlecode.jsf2inspector.plugin.commons.AbstractBeansInspector;
import com.googlecode.jsf2inspector.plugin.commons.InspectionMessage;
import org.apache.maven.plugin.MojoExecutionException;
import org.codehaus.plexus.util.FileUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import javax.faces.bean.ViewScoped;
import java.io.File;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class NonSerializableBeanInspector extends AbstractBeansInspector
{
    @Override
    @SuppressWarnings("unchecked")
    public Set<Class<?>> getObjectsToInspect() throws MojoExecutionException {
        Set<Class<?>> objects = new HashSet<Class<?>>();
        ClassLoader classLoader = processMojo.getClassLoader();

        // load managed beans defined in Spring config files if configured in this plugin
        if (includeSpringConfig) {
            // TODO
        }

        // load managed beans with annotations at first, because declarations in faces config files have precedence
        if (includeAnnotations) {
            List<String> sourceRoots = processMojo.getProject().getCompileSourceRoots();
            String srcRoot = null;
            try {
                for (String sourceRoot : sourceRoots) {
                    srcRoot = sourceRoot;
                    List<String> fileNames = FileUtils.getFileNames(new File(srcRoot), "**/*.java", null, true);
                    for (String fileName : fileNames) {
                        try {
                            Class<?> clazz = classLoader.loadClass(fileName);
                            ManagedBean managedBean = clazz.getAnnotation(ManagedBean.class);
                            if (managedBean != null) {
                                SessionScoped sessionScoped = clazz.getAnnotation(SessionScoped.class);
                                ViewScoped viewScoped = clazz.getAnnotation(ViewScoped.class);
                                if (sessionScoped != null || viewScoped != null) {
                                    objects.add(clazz);
                                }
                            }
                        } catch (ClassNotFoundException e) {
                            // don't break running, just log error
                            processMojo.getLog().error("Class instance of the file '" + fileName + "' could not be loaded", e);
                        }
                    }
                }
            } catch (Exception e) {
                throw new MojoExecutionException("Failed to get source files from " + srcRoot, e);
            }
        }

        // load managed beans according to faces config files
        if (includeFacesConfig) {
            for (Object obj : processMojo.getFacesConfigFiles()) {
                File facesConfigFile = (File) obj;
                Document doc;

                try {
                    if (verboseOutput) {
                        processMojo.getLog().debug("Parsing faces config file '" + facesConfigFile + "' in order to find managed beans ...");
                    }
                    
                    doc = processMojo.getDocumentBuilder().parse(facesConfigFile);
                } catch (Exception e) {
                    throw new MojoExecutionException("Error while parsing faces config file '" + facesConfigFile + "'", e);
                }

                NodeList managedBeanNodes = doc.getElementsByTagName("managed-bean");
                for (int i = 0; i < managedBeanNodes.getLength(); i++) {
                    Node node = managedBeanNodes.item(i);

                    String managedBeanClassName = getBeanForProcessing(node);
                    if (managedBeanClassName == null) {
                        continue;
                    }

                    try {
                        Class<?> clazz = classLoader.loadClass(managedBeanClassName);
                        objects.add(clazz);
                    } catch (ClassNotFoundException e) {
                        // don't break running, just log error
                        processMojo.getLog().error("Class instance of the managed bean '" + managedBeanClassName + "' could not be loaded", e);
                    }
                }
            }
        }

        return objects;
    }

    @Override
    public List<InspectionMessage> inspect(Class<?> clazz) throws MojoExecutionException {
        List<InspectionMessage> inspectionMsgList = null;
        boolean assignableFrom = Serializable.class.isAssignableFrom(clazz);
        if (!assignableFrom) {
            inspectionMsgList = new ArrayList<InspectionMessage>();
            inspectionMsgList.add(new InspectionMessage(clazz.toString(), "Implementation of java.io.Serializable interface is missing"));
        }

        return inspectionMsgList;
    }

    private String getBeanForProcessing(Node node) {
        // check "managed-bean-scope" and return "managed-bean-class" if it's "view" or "session"
        boolean isProperScoped = false;
        String className = null;

        NodeList childs = node.getChildNodes();
        for (int j = 0; j < childs.getLength(); j++) {
            Node child = childs.item(j);
            String textContent = child.getTextContent().trim();

            if ("managed-bean-scope".equals(child.getNodeName())) {
                if ("view".equals(textContent) || "session".equals(textContent)) {
                    isProperScoped = true;
                }
            } else if ("managed-bean-class".equals(child.getNodeName())) {
                className = textContent;
            }
        }

        if (isProperScoped && className != null) {
            if (verboseOutput) {
                processMojo.getLog().debug("Found managed bean '" + className + "', try to load its class information");
            }
            return className;
        }

        return null;
    }
}